#define __CL_ENABLE_EXCEPTIONS
#include "init_opencl.hpp"
#include "matrix_operations.hpp"
#include <time.h>
#include <fstream>
#include <iostream>
#include <CL/cl.hpp>

float * findProductCsrOCL(OpenCLEnv openCLEnv, MatrixOperations matrix, MatrixOperations v, int nrTimes, bool useImg) {
	clock_t init, final;
	int rows = matrix.getNumRows();
	int columns = matrix.getNumCols();
	int nonZeros = matrix.getNumNonZeros();
	float * mValues = matrix.getCooPackValues();
	float * vValues = (float*) v.getMatrix();
//	int * rowIdx = matrix.getCooPackRowIdx();
	int * colIdx = matrix.getCooPackColIdx();
	int * rowPtr = matrix.getCsrRowPtr();

	float * answer = new float[rows];

	cl::Context context = openCLEnv.getContext();
	cl::CommandQueue queue = openCLEnv.getQueue();

	cl::NDRange global(rows);
	cl::NDRange local(1);

	if(useImg == true) { //vector as an image
		// Read source file
		ifstream sourceFile("csr_opencl_img.cl");
		string sourceCode(
			istreambuf_iterator<char>(sourceFile),
			(istreambuf_iterator<char>()));
		cl::Program::Sources source(1, make_pair(sourceCode.c_str(), sourceCode.length()+1));

		// Make program of the source code in the context
		cl::Program program = cl::Program(context, source);

		// Build program for these specific devices
		program.build(openCLEnv.getDevices());

		//In cl::ImageFormat :
		//CL_FLOAT - float values
		//CL_R - 1 value per coord, RGBA - 4 values per coord (pixel)
		cl::Image2D* img = new cl::Image2D(context,CL_MEM_READ_ONLY,cl::ImageFormat(CL_RGBA, CL_FLOAT),1,8192,0);

		// The origin is the same as the indices for the first value
		// The z component must be 0 for 2D images
		cl::size_t<3> origin;
		origin.push_back(0);
		origin.push_back(0);
		origin.push_back(0);

		// The region is the same as the dimensions of the image
		// The z component must be 1 for 2D images
		cl::size_t<3> region;
		region.push_back(1);
		region.push_back(columns/4+1);
		region.push_back(1);

		// The row pitch is the number of bytes in each row
		size_t row_pitch = 4 * sizeof(float);

		queue.enqueueWriteImage(*img,
			CL_TRUE,
			origin, region,
			row_pitch, 0,
			(void*) vValues);

		cl::Kernel kernel(program, "csr_opencl_img");

		cl::Buffer bufferValues = cl::Buffer(context, CL_MEM_READ_ONLY, nonZeros * sizeof(float));
		cl::Buffer bufferColIdx = cl::Buffer(context, CL_MEM_READ_ONLY, nonZeros * sizeof(int));
		cl::Buffer bufferRowPtr = cl::Buffer(context, CL_MEM_READ_ONLY, (rows+1) * sizeof(int));
		cl::Buffer bufferAnswer = cl::Buffer(context, CL_MEM_WRITE_ONLY, rows * sizeof(float));

		queue.enqueueWriteBuffer(bufferValues, CL_TRUE, 0, nonZeros * sizeof(float), mValues);
		queue.enqueueWriteBuffer(bufferColIdx, CL_TRUE, 0, nonZeros * sizeof(int), colIdx);
		queue.enqueueWriteBuffer(bufferRowPtr, CL_TRUE, 0, (rows+1) * sizeof(int), rowPtr);

		kernel.setArg(0, bufferValues);
		kernel.setArg(1, *img);
		kernel.setArg(2, bufferColIdx);
		kernel.setArg(3, bufferRowPtr);
		kernel.setArg(4, rows);
		kernel.setArg(5, bufferAnswer);

		init=clock();
		for(int i = 0; i < nrTimes; i++) {
			queue.enqueueNDRangeKernel(kernel, cl::NullRange, global, local);
			queue.enqueueReadBuffer(bufferAnswer, CL_TRUE, 0, rows * sizeof(float), answer);
		}
		final=clock()-init;

	} else { //vector as an array
		// Read source file
		ifstream sourceFile("csr_opencl.cl");
		string sourceCode(
			istreambuf_iterator<char>(sourceFile),
			(istreambuf_iterator<char>()));
		cl::Program::Sources source(1, make_pair(sourceCode.c_str(), sourceCode.length()+1));

		// Make program of the source code in the context
		cl::Program program = cl::Program(context, source);

		// Build program for these specific devices
		program.build(openCLEnv.getDevices());

		cl::Kernel kernel(program, "csr_opencl");

		cl::Buffer bufferValues = cl::Buffer(context, CL_MEM_READ_ONLY, nonZeros * sizeof(float));
		cl::Buffer bufferVector = cl::Buffer(context, CL_MEM_READ_ONLY, columns * sizeof(float));
		cl::Buffer bufferColIdx = cl::Buffer(context, CL_MEM_READ_ONLY, nonZeros * sizeof(int));
		cl::Buffer bufferRowPtr = cl::Buffer(context, CL_MEM_READ_ONLY, (rows+1) * sizeof(int));
		cl::Buffer bufferAnswer = cl::Buffer(context, CL_MEM_WRITE_ONLY, rows * sizeof(float));

		queue.enqueueWriteBuffer(bufferValues, CL_TRUE, 0, nonZeros * sizeof(float), mValues);
		queue.enqueueWriteBuffer(bufferVector, CL_TRUE, 0, columns * sizeof(float), vValues);
		queue.enqueueWriteBuffer(bufferColIdx, CL_TRUE, 0, nonZeros * sizeof(int), colIdx);
		queue.enqueueWriteBuffer(bufferRowPtr, CL_TRUE, 0, (rows+1) * sizeof(int), rowPtr);


		kernel.setArg(0, bufferValues);
		kernel.setArg(1, bufferVector);
		kernel.setArg(2, bufferColIdx);
		kernel.setArg(3, bufferRowPtr);
		kernel.setArg(4, rows);
		kernel.setArg(5, bufferAnswer);

		init=clock();
		for(int i = 0; i < nrTimes; i++) {
			queue.enqueueNDRangeKernel(kernel, cl::NullRange, global, local);
			queue.enqueueReadBuffer(bufferAnswer, CL_TRUE, 0, rows * sizeof(float), answer);
		}
		final=clock()-init;
	}





	double time = (double)final / ((double)CLOCKS_PER_SEC);
	cout << "Calculation took: " << time << " seconds. (CSR OpenCL, vector as img: " << useImg << ", running kernel)" << endl;
	cout << "CSR opencl, time per: " << time/nrTimes << endl;

	return answer;
}
